﻿#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using BitwiseOre.Properties;
using BitwiseOre.SharedMemory.Core.Common;

namespace BitwiseOre.SharedMemory.Core.IPCQueues
{
    [SharedFactory]
    internal sealed class ConnectionQueues : IConnectionQueues
    {
        private static readonly string UnexpectedEndOfEnumerationMessage = Resources.ConnectionQueues_UnexpectedEndOfEnumerationMessage;
        private static readonly string PurgingReadQueueFailedMessage = Resources.ConnectionQueues_PurgingReadQueueFailedMessage;
        private static readonly string ShutDownWriteFailure = Resources.ConnectionQueues_ShutDownWriteFailure;

        private readonly IGuardedEnumerator<IWriteBuffer> _writeBuffers;
        private readonly IGuardedEnumerator<IReadBuffer> _readBuffers;

        public ConnectionQueues(IGuardedEnumerator<IWriteBuffer> writeBuffers,
                                IGuardedEnumerator<IReadBuffer> readBuffers)
        {
            _writeBuffers = writeBuffers;
            _readBuffers = readBuffers;
        }

        public bool IsCurrentWriteBuffer
        {
            get { return _writeBuffers.Current != null; }
        }

        public bool IsCurrentWriteBufferEmpty
        {
            get { return IsCurrentWriteBuffer && _writeBuffers.Current.IsEmpty; }
        }

        public Task ShutdownWrite(TimeSpan timeOut)
        {
            _writeBuffers.Flush();
            return _writeBuffers.MoveNext(timeOut)
                .Then(success =>
                          {
                              if (!success) throw new InvalidOperationException(ShutDownWriteFailure);

                              _writeBuffers.Flush();
                              _writeBuffers.EndEnumeration();
                          });
        }

        public void FlushWriteBuffer()
        {
            _writeBuffers.Flush();
        }

        public void FlushReadBuffer()
        {
            if (_readBuffers.Current == null || !_readBuffers.Current.IsPastEnd) return;

            _readBuffers.Flush();
        }

        public void PurgeReadBuffers(ITimeoutHelper helper)
        {
            if (_readBuffers.Current == null)
            {
                _readBuffers.MoveNext(helper.RemainingTime)
                    .Then(success =>
                              {
                                  if (!success) throw new InvalidOperationException(PurgingReadQueueFailedMessage);
                                  PurgeReadBuffersCore(helper);
                              });
            }
            else
            {
                PurgeReadBuffersCore(helper);
            }
        }

        private void PurgeReadBuffersCore(ITimeoutHelper helper)
        {
            if (_readBuffers.Current.IsEmpty) return;

            _readBuffers.MoveNext(helper.RemainingTime)
                .Then(success =>
                          {
                              if (!success) throw new InvalidOperationException(PurgingReadQueueFailedMessage);
                              PurgeReadBuffersCore(helper);
                          });
        }

        public void WriteToNextBuffer(ArraySegment<byte> array, ITimeoutHelper helper)
        {
            _writeBuffers.MoveNext(helper.RemainingTime)
                .Then(
                    success =>
                        {
                            if (success)
                            {
                                WriteToCurrentBuffer(array, helper);
                            }
                            else
                            {
                                throw new InvalidOperationException(UnexpectedEndOfEnumerationMessage);
                            }
                        });
        }

        public void WriteToCurrentBuffer(ArraySegment<byte> array, ITimeoutHelper helper)
        {
            var actual = _writeBuffers.Current.WriteArraySegment(array);
            if (actual >= array.Count) return;

            var newarray = new ArraySegment<byte>(array.Array, array.Offset + actual, array.Count - actual);
            WriteToNextBuffer(newarray, helper);
        }

        public bool WriteByteToCurrentBuffer(byte value)
        {
            return _writeBuffers.Current.WriteByte(value);
        }

        public int ReadByteFromCurrentBuffer()
        {
            return _readBuffers.Current != null
                       ? _readBuffers.Current.ReadByte()
                       : -1;
        }

        public int ReadFromCurrentBuffer(ArraySegment<byte> array)
        {
            return _readBuffers.Current != null
                       ? _readBuffers.Current.ReadArraySegment(array)
                       : 0;
        }

        public Task<int> ReadFromNextBuffer(ArraySegment<byte> array, ITimeoutHelper helper)
        {
            if (_readBuffers.Current != null)
                _readBuffers.Current.Clear();

            return _readBuffers.MoveNext(helper.RemainingTime)
                .Then(success =>
                          {
                              if (!success) throw new InvalidOperationException(UnexpectedEndOfEnumerationMessage);

                              if (_readBuffers.Current.IsEmpty)
                              {
                                  ShutdownWrite(helper.RemainingTime);
                                  return 0;
                              }

                              return ReadFromCurrentBuffer(array);
                          });
        }
    }
}